Introduction to R - Part II

Recap

We should have loaded the readr and dplyr libraries and imported an example dataset into R.

library(readr)
package 㤼㸱readr㤼㸲 was built under R version 4.0.2
library(dplyr)
gapminder <- read_csv("raw_data/gapminder.csv")

“Piping”

As have have just seen, we will often need to perform an analysis, or clean a dataset, using several dplyr functions in sequence. e.g. filtering, mutating, then selecting columns of interest (possibly followed by plotting - see shortly).

As a small example; if we wanted to filter our results to just Europe the continent column becomes redundant so we might as well remove it.

The following is perfectly valid R code, but invites the user to make mistakes and copy-and-paste erros when writing it. We also have to create multiple copies of the same data frame.

tmp <- filter(gapminder, continent == "Europe")
tmp2 <- select(tmp, -continent)
tmp2

(Those familiar with Unix may recall that commands can be joined with a pipe; |)

In R, dplyr commands to be linked together and form a workflow. The symbol %>% is pronounced then. With a %>% the input to a function is assumed to be the output of the previous line. All the dplyr functions that we have seen so far take a data frame as an input and return an altered data frame as an output, so are amenable to this type of programming.

The example we gave of filtering just the European countries and removing the continent column becomes:-

filter(gapminder, continent=="Europe") %>% 
  select(-continent)

We can join as many dplyr functions as we require for the analysis.




Exercise

  • Re-write your solution to the previous exercise, but using the %>% symbol



Plotting

The R language has extensive graphical capabilities.

Graphics in R may be created by many different methods including base graphics and more advanced plotting packages such as lattice.

The ggplot2 package was created by Hadley Wickham and provides a intuitive plotting system to rapidly generate publication quality graphics.

ggplot2 builds on the concept of the “Grammar of Graphics” (Wilkinson 2005, Bertin 1983) which describes a consistent syntax for the construction of a wide range of complex graphics by a concise description of their components.

Why use ggplot2?

The structured syntax and high level of abstraction used by ggplot2 should allow for the user to concentrate on the visualisations instead of creating the underlying code.

On top of this central philosophy ggplot2 has:

  • Increased flexibility over many plotting systems.
  • An advanced theme system for professional/publication level graphics.
  • Large developer base – Many libraries extending its flexibility.
  • Large user base – Great documentation and active mailing list.

It is always useful to think about the message you want to convey and the appropriate plot before writing any R code. Resources like data-to-viz.com should help.

With some practice, ggplot2 makes it easier to go from the figure you are imagining in our head (or on paper) to a publication-ready image in R.

As with dplyr, we won’t have time to cover all details of ggplot2. This is however a useful cheatsheet that can be printed as a reference. The cheatsheet is also available through the RStudio Help menu.

Basic plot types

A plot in ggplot2 is created with the following type of command

ggplot(data = <DATA>, mapping = aes(<MAPPINGS>)) +  <GEOM_FUNCTION>()

So we need to specify

  • The data to be used in graph
  • Mappings of data to the graph (aesthetic mapping)
  • What type of graph we want to use (The geom to use).

Lets say that we want to explore the relationship between GDP and Life Expectancy. We might start with the hypothesis that richer countries have higher life expectancy. A sensible choice of plot would be a scatter plot with gdp on the x-axis and life expectancy on the y-axis.

The first stage is to specify our dataset

library(ggplot2)
package 㤼㸱ggplot2㤼㸲 was built under R version 4.0.2
ggplot(data = gapminder)

For the aesthetics, as a bare minimum we will map the gdpPercap and lifeExp to the x- and y-axis of the plot. Some progress is made; we at least get axes

ggplot(data = gapminder,aes(x=gdpPercap, y=lifeExp))

That created the axes, but we still need to define how to display our points on the plot. As we have continuous data for both the x- and y-axis, geom_point is a good choice.

ggplot(data = gapminder,aes(x=gdpPercap, y=lifeExp)) + geom_point()

The geom we use will depend on what kind of data we have (continuous, categorical etc)

  • geom_point() - Scatter plots
  • geom_line() - Line plots
  • geom_smooth() - Fitted line plots
  • geom_bar() - Bar plots
  • geom_boxplot() - Boxplots
  • geom_jitter() - Jitter to plots
  • geom_histogram() - Histogram plots
  • geom_density() - Density plots
  • geom_text() - Text to plots
  • geom_errorbar() - Errorbars to plots
  • geom_violin() - Violin plots
  • geom_tile() - for “heatmap”-like plots

Boxplots are commonly used to visualise the distributions of continuous data. We have to use a categorical variable on the x-axis such as continent or country (not advisable in this case as there are too many different values).

The order of the boxes along the x-axis is dictated by the order of categories in the factor; with the default for names being alphabetical order.

ggplot(gapminder, aes(x = continent, y=gdpPercap)) + geom_boxplot()

ggplot(gapminder, aes(x = gdpPercap)) + geom_histogram()

Producing a barplot of counts only requires an x variable. The counts will be generated by R.

ggplot(gapminder, aes(x=continent)) + geom_bar()

The height of the bars can also be mapped directly to numeric variables in the data frame if the geom_col function is used instead.

In the below plot the axis labels will be messy and difficult to read. This is something that can be customised with some of the ggplot2 options we will explore later.

gapminder2002 <- filter(gapminder, year==2002,continent=="Americas")
ggplot(gapminder2002, aes(x=country,y=gdpPercap)) + geom_col()

Where appropriate, we can add multiple layers of geoms to the plot. For instance, a criticism of the boxplot is that it does not show all the data. We can rectify this by overlaying the individual points.

ggplot(gapminder, aes(x = continent, y=gdpPercap)) + geom_boxplot() + geom_point()

ggplot(gapminder, aes(x = continent, y=gdpPercap)) + geom_boxplot() + geom_jitter(width=0.1)




Exercises

  • The violin plot is a popular alternative to the boxplot. Create a violin plot with geom_violin to visualise the differences in GDP between different continents.
  • Create a subset of the gapminder data frame containing just the rows for your country of birth
  • Has there been an increase in life expectancy over time?
    • visualise the trend using a scatter plot (geom_point), line graph (geom_line) or smoothed line (geom_smooth).
  • What happens when you modify the geom_boxplot example to compare the gdp distributions for different years?
    • Look at the message ggplot2 prints above the plot and try to modify the code to give a separate boxplot for each year



As we have seen already, ggplot offers an interface to create many popular plot types. It is up to the user to decide what the best way to visualise the data.

Customising the plot appearance

Our plots are a bit dreary at the moment, but one way to add colour is to add a col argument to the geom_point function. The value can be any of the pre-defined colour names in R. These are displayed in this handy online reference. Red, Green, Blue of Hex values can also be given.

ggplot(gapminder, aes(x = gdpPercap, y=lifeExp)) + geom_point(col="red")

However, a powerful feature of ggplot2 is that colours are treated as aesthetics of the plot. In other words we can use a column in our dataset.

Let’s say that we want points on our plot to be coloured according to continent. We add an extra argument to the definition of aesthetics to define the mapping. ggplot2 will even decide on colours and create a legend for us.

ggplot(gapminder, aes(x = gdpPercap, y=lifeExp,col=continent)) + geom_point()

It will even choose a continuous or discrete colour scale based on the data type. We have already seen that ggplot2 is treat our year column as numerical data; which is probably not very useful for visualisation.

ggplot(gapminder, aes(x = gdpPercap, y=lifeExp,col=year)) + geom_point()

We can force ggplot2 to treat year as categorical data by using as.factor when creating the aesthetics.

ggplot(gapminder, aes(x = gdpPercap, y=lifeExp,col=as.factor(year))) + geom_point()

When used in the construction of a boxplot, the col argument will change the colour of the lines. To change the colour of the boxes we have to use fill.

ggplot(gapminder, aes(x = continent, y=gdpPercap,fill=continent)) + geom_boxplot()

The shape and size of points can also be mapped from the data. However, it is easy to get carried away.

ggplot(gapminder, aes(x = gdpPercap, y=lifeExp,shape=continent,size=pop)) + geom_point()

Scales and their legends have so far been handled using ggplot2 defaults. ggplot2 offers functionality to have finer control over scales and legends using the scale methods.

Scale methods are divided into functions by combinations of

  • the aesthetics they control.

  • the type of data mapped to scale.

scale_aesthetic_type

Try typing in scale_ then tab to autocomplete. This will provide some examples of the scale functions available in ggplot2.

Although different scale functions accept some variety in their arguments, common arguments to scale functions include -

  • name - The axis or legend title

  • limits - Minimum and maximum of the scale

  • breaks - Label/tick positions along an axis

  • labels - Label names at each break

  • values - the set of aesthetic values to map data values

We can choose specific colour palettes, such as those provided by the RColorBrewer package. This package provides palettes for different types of scale (sequential, diverging, qualitative).

library(RColorBrewer)
display.brewer.all(colorblindFriendly = TRUE)

When creating a plot, always check that the colour scheme is appropriate for people with various forms of colour-blindness

When experimenting with colour palettes and labels, it is useful to save the plot as an object

p <- ggplot(gapminder, aes(x = gdpPercap, y=lifeExp,col=continent)) + geom_point()
# We can also change the text displayed above the legend with the name parameter.
p + scale_color_brewer(name="Continent", palette = "Set2")

Or we can even specify our own colours; such as The University of Sheffield branding colours

my_pal <- c(rgb(0,159,218,maxColorValue = 255),
            rgb(31,20,93,maxColorValue = 255),
            rgb(249,227,0,maxColorValue = 255),
            rgb(0,155,72,maxColorValue = 255),
            rgb(190,214,0,maxColorValue = 255))
p + scale_color_manual(values=my_pal)

Various labels can be modified using the labs function.

p + labs(x="Wealth",y="Life Expectancy",title="Relationship between Wealth and Life Expectancy")

We can also modify the x- and y- limits of the plot so that any outliers are not shown. ggplot2 will give a warning that some points are excluded.

p + xlim(0,60000)

Saving is supported by the ggsave function. A variety of file formats are supported (.png, .pdf, .tiff, etc) and the format used is determined from the extension given in the file argument. The height, width and resolution can also be configured. See the help on ggsave (?ggsave) for more information.

ggsave(p, file="my_ggplot.png")
Saving 7 x 7 in image

Most aspects of the plot can be modified from the background colour to the grid sizes and font. Several pre-defined “themes” exist and we can modify the appearance of the whole plot using a theme_.. function.

p + theme_bw()

More themes are supported by the ggthemes package. You can make your plots look like the Economist, Wall Street Journal or Excel (but please don’t do this!)




Facets

One very useful feature of ggplot2 is faceting. This allows you to produce plots subset by variables in your data. In the scatter plot above, it was quite difficult to see if the relationship between gdp and life expectancy was the same for each continent. To overcome this, we would like a see a separate plot for each continent.

To facet our data into multiple plots we can use the facet_wrap (1 variable) or facet_grid (2 variables) functions and specify the variable(s) we split by.

p + facet_wrap(~continent)

The facet_grid function will create a grid-like plot with one variable on the x-axis and another on the y-axis.

p + facet_grid(continent~year)

The previous plot was a bit messy as it contained all combinations of year and continent. Let’s suppose we want our analysis to be a bit more focused and disregard countries in Oceania (as there are only 2 in our dataset) and maybe years between 1997 and 2002.

We should know how to restrict the rows from the gapminder dataset using the filter function. Instead of filtering the data, creating a new data frame and constructing the data frame from these new data we can use the%>% operator to create the data frame on the fly and pass directly to ggplot. Thus we don’t have to save a new data frame or alter the original data.

filter(gapminder, continent!="Oceania", year %in% c(1997,2002,2007)) %>% 
  ggplot(aes(x = gdpPercap, y=lifeExp,col=continent)) + geom_point() + facet_grid(continent~year)

Summarising and grouping with dplyr

The summarise function can take any R function that takes a vector of values (i.e. a column from a data frame) and returns a single value. Some of the more useful functions include:

  • min minimum value
  • max maximum value
  • sum sum of values
  • mean mean value
  • sd standard deviation
  • median median value
  • IQR the interquartile range
  • n_distinct the number of distinct values
  • n the number of observations (Note: this is a special function that doesn’t take a vector argument, i.e. column)
library(dplyr)
summarise(gapminder, min(lifeExp), max(gdpPercap), mean(pop))

It is also possible to summarise using a function that takes more than one value, i.e. from multiple columns. For example, we could compute the correlation between year and life expectancy. Here we also assign names to the table that is produced.

gapminder %>% 
summarise(MinLifeExpectancy = min(lifeExp), 
          MaximumGDP = max(gdpPercap), 
          AveragePop = mean(pop), 
          Correlation = cor(year, lifeExp))

However, it is not particularly useful to calculate such values from the entire table as we have different continents and years. The group_by function allows us to split the table into different categories, and compute summary statistics for each year (for example).

gapminder %>% 
    group_by(year) %>% 
    summarise(MinLifeExpectancy = min(lifeExp), 
              MaximumGDP = max(gdpPercap), 
              AveragePop = mean(pop))
`summarise()` ungrouping output (override with `.groups` argument)

If we want to summarise several columns we can use the convenient summarise_all function. However, this will return NA values for columns that do not contain numeric values.

gapminder %>% 
  group_by(continent,year) %>% 
  summarise_all(mean)

The nice thing about summarise is that it can followed up by any of the other dplyr verbs that we have met so far (select, filter, arrange..etc). As the country column of the previous output containing missing values we can exclude it from further processing.

gapminder %>% 
  group_by(continent,year) %>% 
  summarise_all(mean) %>% 
  select(-country)

Returning to the correlation between life expectancy and year, we can summarise as follows:-

gapminder %>%     
    group_by(country) %>% 
    summarise(Correlation = cor(year , lifeExp))
`summarise()` ungrouping output (override with `.groups` argument)

We can then arrange the table by the correlation to see which countries have the lowest correlation

gapminder %>%      
    group_by(country) %>% 
    summarise(Correlation = cor(year , lifeExp)) %>% 
    arrange(Correlation)
`summarise()` ungrouping output (override with `.groups` argument)

We can filter the results to find observations of interest

gapminder %>%      
    group_by(country) %>% 
    summarise(Correlation = cor(year , lifeExp)) %>% 
    filter(Correlation < 0)
`summarise()` ungrouping output (override with `.groups` argument)

The countries we identify could then be used as the basis for a plot.

library(ggplot2)
filter(gapminder, country %in% c("Rwanda","Zambia","Zimbabwe")) %>% 
  ggplot(aes(x=year, y=lifeExp,col=country)) + geom_line()




Exercise

  • Summarise the gapminder data in an appropriate manner to produce a plot to show the change in average gdpPercap for each continent over time.
  • see below for a suggestion
    • HINT: you will need to use the geom_col function to create the bar plot



Joining

In many real life situations, data are spread across multiple tables or spreadsheets. Usually this occurs because different types of information about a subject, e.g. a patient, are collected from different sources. It may be desirable for some analyses to combine data from two or more tables into a single data frame based on a common column, for example, an attribute that uniquely identifies the subject.

dplyr provides a set of join functions for combining two data frames based on matches within specified columns. For those familiar with such SQL, these operations are very similar to carrying out join operations between tables in a relational database.

As a toy example, lets consider two data frames that some results of testing whether genes A, B and C are significant in our study (gene expression, mutations, etc.)

gene_results <- data.frame(Name=LETTERS[1:3], pvalue = c(0.001, 0.1,0.01))
gene_results

We might also have a data frame containing more data about the genes; such as which chromosome they are located on. As part of our data interpretation we might need to know where in the genome the genes are located.

gene_anno <- data.frame(Name = c("A","B","D"), chromosome=c(1,1,3))
gene_anno

There are various ways in which we can join these two tables together. We will first consider the case of a “left join”.

Animated gif by Garrick Aden-Buie

left_join returns all rows from the first data frame regardless of whether there is a match in the second data frame. Rows with no match are included in the resulting data frame but have NA values in the additional columns coming from the second data frame.

Animations to illustrate other types of join are available at https://github.com/gadenbuie/tidy-animated-verbs

left_join(gene_results, gene_anno)
Joining, by = "Name"

right_join is similar but returns all rows from the second data frame that have a match with rows in the first data frame based on the specified column.

right_join(gene_results, gene_anno)
Joining, by = "Name"

inner_join only returns those rows where matches could be made

inner_join(gene_results, gene_anno)
Joining, by = "Name"



Wrap-up

We have introduced a few of the essential packages from the R tidyverse that can help with data manipulation and visualisation.

Hopefully you will feel more confident about importing your data into R and producing some useful visualisations. You will probably have questions regarding the analysis of your own data. Some good starting points to get help are listed below.

To finish the workshop we will look at the analysis of some relevant data that we can import into R and analyse with the tools from the workshop.

Introducing the COVID-19 data

Data for global COVID-19 cases are available online from CSSE at Johns Hopkins University on their github repository.

github is an excellent way of making your code and analysis available for others to reuse and share. Private repositories with restricted access are also available. Here is a useful beginners guide.

-Friendly github intro

R is capable of downloading files to our own machine so we can analyse them. We need to know the URL (for the COVID data we can find this from github, or use the address below) and can specify what to call the file when it is downloaded.

download.file("https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_confirmed_global.csv",destfile = "time_series_covid19_confirmed_global.csv")
trying URL 'https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_confirmed_global.csv'
Content type 'text/plain; charset=utf-8' length 315165 bytes (307 KB)
downloaded 307 KB

We can use the read_csv function as before to import the data and take a look. We can see the basic structure of the data is one row for each country / region and columns for cases on each day.

covid <- read_csv("time_series_covid19_confirmed_global.csv")
Parsed with column specification:
cols(
  .default = col_double(),
  `Province/State` = col_character(),
  `Country/Region` = col_character()
)
See spec(...) for full column specifications.
covid

We can potentially join these data to gapminder, but it would be beneficial to have one column name in common between both files. We can rename the Country/Region column of our new data frame to match gapminder.

covid <- read_csv("time_series_covid19_confirmed_global.csv") %>% 
  rename(country = `Country/Region`) 
Parsed with column specification:
cols(
  .default = col_double(),
  `Province/State` = col_character(),
  `Country/Region` = col_character()
)
See spec(...) for full column specifications.
covid

Much of the analysis of this dataset has looked at trends over time (e.g. increasing /decreasing case numbers, comparing trajectories). As we know by now, the ggplot2 package allows us to map columns (variables) in our dataset to aspects of the plot.

In other words, we would expect to create plots by writing code such as:-

ggplot(covid, aes(x = Date, y =...)) + ...

Unfortunately such plots are not possible with the data in it’s current format. Counts for each date are containing in a different column. What we require is a column to indicate the date, and the corresponding count in the next column. Such data arrangements are known as long data; whereas we have wide data. Fortunately we can convert between the two using the tidyr package (also part of tidyverse).

## install tidyr if you don't already have it
install.packages("tidyr")

For more information on tidy data, and how to convert between long and wide data, see

https://r4ds.had.co.nz/tidy-data.html


covid <- read_csv("time_series_covid19_confirmed_global.csv") %>% 
  rename(country = `Country/Region`) %>% 
  tidyr::pivot_longer(5:last_col(),names_to="Date", values_to="Cases")
Parsed with column specification:
cols(
  .default = col_double(),
  `Province/State` = col_character(),
  `Country/Region` = col_character()
)
See spec(...) for full column specifications.
covid

Another point to note is that the dates are not in an internationally recognised format, which could cause a problem for some visualisations that rely on date order. We can fix by explicitly converting to YYYY-MM-DD format.

covid <- read_csv("time_series_covid19_confirmed_global.csv") %>% 
  rename(country = `Country/Region`) %>% 
  tidyr::pivot_longer(5:last_col(),names_to="Date", values_to="Cases") %>% 
    mutate(Date=as.Date(Date,"%m/%d/%y"))
Parsed with column specification:
cols(
  .default = col_double(),
  `Province/State` = col_character(),
  `Country/Region` = col_character()
)
See spec(...) for full column specifications.
covid

The number of new cases per-day can also be added to the table by using the lag function from dplyr.

covid <- read_csv("time_series_covid19_confirmed_global.csv") %>% 
  rename(country = `Country/Region`) %>% 
  tidyr::pivot_longer(5:last_col(),names_to="Date", values_to="Cases") %>% 
  mutate(Date=as.Date(Date,"%m/%d/%y")) %>% 
  mutate(DailyCases = Cases - dplyr::lag(Cases,default = 0))  
Parsed with column specification:
cols(
  .default = col_double(),
  `Province/State` = col_character(),
  `Country/Region` = col_character()
)
See spec(...) for full column specifications.
covid

Exercise

What plots and summaries can you make from these data?

  • Plotting the number of cases over time for certain countries
  • For each continent, what countries have the highest number of cases?
  • Can you normalise for population size (using 2007 population figures)?
LS0tDQp0aXRsZTogIlIgQ3Jhc2ggQ291cnNlIg0KYXV0aG9yOiAiTWFyayBEdW5uaW5nIg0KZGF0ZTogJ2ByIGZvcm1hdChTeXMudGltZSgpLCAiTGFzdCBtb2RpZmllZDogJWQgJWIgJVkiKWAnDQpvdXRwdXQ6IA0KICBodG1sX25vdGVib29rOiANCiAgICB0b2M6IHllcw0KICAgIHRvY19mbG9hdDogeWVzDQogICAgY3NzOiBzdHlsZXNoZWV0cy9zdHlsZXMuY3NzDQplZGl0b3Jfb3B0aW9uczogDQogIGNodW5rX291dHB1dF90eXBlOiBpbmxpbmUNCi0tLQ0KDQojIEludHJvZHVjdGlvbiB0byBSIC0gUGFydCBJSQ0KDQo8aW1nIHNyYz0iaW1hZ2VzL2xvZ28tc20ucG5nIiBzdHlsZT0icG9zaXRpb246YWJzb2x1dGU7dG9wOjQwcHg7cmlnaHQ6MTBweDsiIHdpZHRoPSIyMDAiIC8+DQoNCiMjIFJlY2FwDQoNCldlIHNob3VsZCBoYXZlIGxvYWRlZCB0aGUgYHJlYWRyYCBhbmQgYGRwbHlyYCBsaWJyYXJpZXMgYW5kIGltcG9ydGVkIGFuIGV4YW1wbGUgZGF0YXNldCBpbnRvIFIuDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0V9DQpsaWJyYXJ5KHJlYWRyKQ0KbGlicmFyeShkcGx5cikNCmdhcG1pbmRlciA8LSByZWFkX2NzdigicmF3X2RhdGEvZ2FwbWluZGVyLmNzdiIpDQpgYGANCg0KDQoNCiMjIyAiUGlwaW5nIg0KDQpBcyBoYXZlIGhhdmUganVzdCBzZWVuLCB3ZSB3aWxsIG9mdGVuIG5lZWQgdG8gcGVyZm9ybSBhbiBhbmFseXNpcywgb3IgY2xlYW4gYSBkYXRhc2V0LCB1c2luZyBzZXZlcmFsIGBkcGx5cmAgZnVuY3Rpb25zIGluIHNlcXVlbmNlLiBlLmcuIGZpbHRlcmluZywgbXV0YXRpbmcsIHRoZW4gc2VsZWN0aW5nIGNvbHVtbnMgb2YgaW50ZXJlc3QgKHBvc3NpYmx5IGZvbGxvd2VkIGJ5IHBsb3R0aW5nIC0gc2VlIHNob3J0bHkpLg0KDQpBcyBhIHNtYWxsIGV4YW1wbGU7IGlmIHdlIHdhbnRlZCB0byBmaWx0ZXIgb3VyIHJlc3VsdHMgdG8ganVzdCBFdXJvcGUgdGhlIGBjb250aW5lbnRgIGNvbHVtbiBiZWNvbWVzIHJlZHVuZGFudCBzbyB3ZSBtaWdodCBhcyB3ZWxsIHJlbW92ZSBpdC4NCg0KVGhlIGZvbGxvd2luZyBpcyBwZXJmZWN0bHkgdmFsaWQgUiBjb2RlLCBidXQgaW52aXRlcyB0aGUgdXNlciB0byBtYWtlIG1pc3Rha2VzIGFuZCBjb3B5LWFuZC1wYXN0ZSBlcnJvcyB3aGVuIHdyaXRpbmcgaXQuIFdlIGFsc28gaGF2ZSB0byBjcmVhdGUgbXVsdGlwbGUgY29waWVzIG9mIHRoZSBzYW1lIGRhdGEgZnJhbWUuDQoNCmBgYHtyfQ0KdG1wIDwtIGZpbHRlcihnYXBtaW5kZXIsIGNvbnRpbmVudCA9PSAiRXVyb3BlIikNCnRtcDIgPC0gc2VsZWN0KHRtcCwgLWNvbnRpbmVudCkNCnRtcDINCmBgYA0KDQooVGhvc2UgZmFtaWxpYXIgd2l0aCBVbml4IG1heSByZWNhbGwgdGhhdCBjb21tYW5kcyBjYW4gYmUgam9pbmVkIHdpdGggYSBwaXBlOyBgfGApDQoNCkluIFIsIGBkcGx5cmAgY29tbWFuZHMgdG8gYmUgbGlua2VkIHRvZ2V0aGVyIGFuZCBmb3JtIGEgd29ya2Zsb3cuIFRoZSBzeW1ib2wgYCU+JWAgaXMgcHJvbm91bmNlZCAqKnRoZW4qKi4gV2l0aCBhIGAlPiUgYCB0aGUgaW5wdXQgdG8gYSBmdW5jdGlvbiBpcyBhc3N1bWVkIHRvIGJlIHRoZSBvdXRwdXQgb2YgdGhlIHByZXZpb3VzIGxpbmUuIEFsbCB0aGUgYGRwbHlyYCBmdW5jdGlvbnMgdGhhdCB3ZSBoYXZlIHNlZW4gc28gZmFyIHRha2UgYSBkYXRhIGZyYW1lIGFzIGFuIGlucHV0IGFuZCByZXR1cm4gYW4gYWx0ZXJlZCBkYXRhIGZyYW1lIGFzIGFuIG91dHB1dCwgc28gYXJlIGFtZW5hYmxlIHRvIHRoaXMgdHlwZSBvZiBwcm9ncmFtbWluZy4NCg0KVGhlIGV4YW1wbGUgd2UgZ2F2ZSBvZiBmaWx0ZXJpbmcganVzdCB0aGUgRXVyb3BlYW4gY291bnRyaWVzIGFuZCByZW1vdmluZyB0aGUgYGNvbnRpbmVudGAgY29sdW1uIGJlY29tZXM6LQ0KDQoNCmBgYHtyfQ0KZmlsdGVyKGdhcG1pbmRlciwgY29udGluZW50PT0iRXVyb3BlIikgJT4lIA0KICBzZWxlY3QoLWNvbnRpbmVudCkNCmBgYA0KDQpXZSBjYW4gam9pbiBhcyBtYW55IGBkcGx5cmAgZnVuY3Rpb25zIGFzIHdlIHJlcXVpcmUgZm9yIHRoZSBhbmFseXNpcy4NCg0KKioqKioqDQoqKioqKioNCioqKioqKg0KDQojIyMjIEV4ZXJjaXNlDQoNCjxkaXYgY2xhc3M9ImV4ZXJjaXNlIj4NCi0gUmUtd3JpdGUgeW91ciBzb2x1dGlvbiB0byB0aGUgcHJldmlvdXMgZXhlcmNpc2UsIGJ1dCB1c2luZyB0aGUgYCAlPiUgYCBzeW1ib2wNCg0KPC9kaXY+DQoNCmBgYHtyfQ0KDQoNCmBgYA0KDQoNCioqKioqKg0KKioqKioqDQoqKioqKioNCg0KIyBQbG90dGluZw0KDQpUaGUgUiBsYW5ndWFnZSBoYXMgZXh0ZW5zaXZlIGdyYXBoaWNhbCBjYXBhYmlsaXRpZXMuDQoNCkdyYXBoaWNzIGluIFIgbWF5IGJlIGNyZWF0ZWQgYnkgbWFueSBkaWZmZXJlbnQgbWV0aG9kcyBpbmNsdWRpbmcgYmFzZSBncmFwaGljcyBhbmQgbW9yZSBhZHZhbmNlZCBwbG90dGluZyBwYWNrYWdlcyBzdWNoIGFzIGxhdHRpY2UuDQoNClRoZSBgZ2dwbG90MmAgcGFja2FnZSB3YXMgY3JlYXRlZCBieSBIYWRsZXkgV2lja2hhbSBhbmQgcHJvdmlkZXMgYSBpbnR1aXRpdmUgcGxvdHRpbmcgc3lzdGVtIHRvIHJhcGlkbHkgZ2VuZXJhdGUgcHVibGljYXRpb24gcXVhbGl0eSBncmFwaGljcy4NCg0KYGdncGxvdDJgIGJ1aWxkcyBvbiB0aGUgY29uY2VwdCBvZiB0aGUg4oCcR3JhbW1hciBvZiBHcmFwaGljc+KAnSAoV2lsa2luc29uIDIwMDUsIEJlcnRpbiAxOTgzKSB3aGljaCBkZXNjcmliZXMgYSBjb25zaXN0ZW50IHN5bnRheCBmb3IgdGhlIGNvbnN0cnVjdGlvbiBvZiBhIHdpZGUgcmFuZ2Ugb2YgY29tcGxleCBncmFwaGljcyBieSBhIGNvbmNpc2UgZGVzY3JpcHRpb24gb2YgdGhlaXIgY29tcG9uZW50cy4NCg0KIyMgV2h5IHVzZSBnZ3Bsb3QyPw0KDQpUaGUgc3RydWN0dXJlZCBzeW50YXggYW5kIGhpZ2ggbGV2ZWwgb2YgYWJzdHJhY3Rpb24gdXNlZCBieSBnZ3Bsb3QyIHNob3VsZCBhbGxvdyBmb3IgdGhlIHVzZXIgdG8gY29uY2VudHJhdGUgb24gdGhlIHZpc3VhbGlzYXRpb25zIGluc3RlYWQgb2YgY3JlYXRpbmcgdGhlIHVuZGVybHlpbmcgY29kZS4NCg0KT24gdG9wIG9mIHRoaXMgY2VudHJhbCBwaGlsb3NvcGh5IGdncGxvdDIgaGFzOg0KDQotIEluY3JlYXNlZCBmbGV4aWJpbGl0eSBvdmVyIG1hbnkgcGxvdHRpbmcgc3lzdGVtcy4NCi0gQW4gYWR2YW5jZWQgdGhlbWUgc3lzdGVtIGZvciBwcm9mZXNzaW9uYWwvcHVibGljYXRpb24gbGV2ZWwgZ3JhcGhpY3MuDQotIExhcmdlIGRldmVsb3BlciBiYXNlIOKAkyBNYW55IGxpYnJhcmllcyBleHRlbmRpbmcgaXRzIGZsZXhpYmlsaXR5Lg0KLSBMYXJnZSB1c2VyIGJhc2Ug4oCTIEdyZWF0IGRvY3VtZW50YXRpb24gYW5kIGFjdGl2ZSBtYWlsaW5nIGxpc3QuDQoNCjxkaXYgY2xhc3M9ImluZm9ybWF0aW9uIj4NCg0KSXQgaXMgYWx3YXlzIHVzZWZ1bCB0byB0aGluayBhYm91dCB0aGUgbWVzc2FnZSB5b3Ugd2FudCB0byBjb252ZXkgYW5kIHRoZSBhcHByb3ByaWF0ZSBwbG90IGJlZm9yZSB3cml0aW5nIGFueSBSIGNvZGUuIFJlc291cmNlcyBsaWtlIFtkYXRhLXRvLXZpei5jb21dKGh0dHBzOi8vd3d3LmRhdGEtdG8tdml6LmNvbS8pIHNob3VsZCBoZWxwLg0KPC9kaXY+DQoNCldpdGggc29tZSBwcmFjdGljZSwgYGdncGxvdDJgIG1ha2VzIGl0IGVhc2llciB0byBnbyBmcm9tIHRoZSBmaWd1cmUgeW91IGFyZSBpbWFnaW5pbmcgaW4gb3VyIGhlYWQgKG9yIG9uIHBhcGVyKSB0byBhIHB1YmxpY2F0aW9uLXJlYWR5IGltYWdlIGluIFIuDQoNCjxkaXYgY2xhc3M9ImluZm9ybWF0aW9uIj4NCkFzIHdpdGggYGRwbHlyYCwgd2Ugd29uJ3QgaGF2ZSB0aW1lIHRvIGNvdmVyIGFsbCBkZXRhaWxzIG9mIGBnZ3Bsb3QyYC4gVGhpcyBpcyBob3dldmVyIGEgdXNlZnVsIFtjaGVhdHNoZWV0XShodHRwczovL3d3dy5yc3R1ZGlvLmNvbS93cC1jb250ZW50L3VwbG9hZHMvMjAxNS8wMy9nZ3Bsb3QyLWNoZWF0c2hlZXQucGRmKSB0aGF0IGNhbiBiZSBwcmludGVkIGFzIGEgcmVmZXJlbmNlLiBUaGUgY2hlYXRzaGVldCBpcyBhbHNvIGF2YWlsYWJsZSB0aHJvdWdoIHRoZSBSU3R1ZGlvIEhlbHAgbWVudS4NCjwvZGl2Pg0KDQojIyBCYXNpYyBwbG90IHR5cGVzDQoNCkEgcGxvdCBpbiBgZ2dwbG90MmAgaXMgY3JlYXRlZCB3aXRoIHRoZSBmb2xsb3dpbmcgdHlwZSBvZiBjb21tYW5kDQoNCmBgYA0KZ2dwbG90KGRhdGEgPSA8REFUQT4sIG1hcHBpbmcgPSBhZXMoPE1BUFBJTkdTPikpICsgIDxHRU9NX0ZVTkNUSU9OPigpDQpgYGANCg0KU28gd2UgbmVlZCB0byBzcGVjaWZ5DQoNCi0gVGhlIGRhdGEgdG8gYmUgdXNlZCBpbiBncmFwaA0KLSBNYXBwaW5ncyBvZiBkYXRhIHRvIHRoZSBncmFwaCAoKmFlc3RoZXRpYyogbWFwcGluZykNCi0gV2hhdCB0eXBlIG9mIGdyYXBoIHdlIHdhbnQgdG8gdXNlIChUaGUgKmdlb20qIHRvIHVzZSkuDQoNCkxldHMgc2F5IHRoYXQgd2Ugd2FudCB0byBleHBsb3JlIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBHRFAgYW5kIExpZmUgRXhwZWN0YW5jeS4gV2UgbWlnaHQgc3RhcnQgd2l0aCB0aGUgaHlwb3RoZXNpcyB0aGF0IHJpY2hlciBjb3VudHJpZXMgaGF2ZSBoaWdoZXIgbGlmZSBleHBlY3RhbmN5LiBBIHNlbnNpYmxlIGNob2ljZSBvZiBwbG90IHdvdWxkIGJlIGEgKnNjYXR0ZXIgcGxvdCogd2l0aCBnZHAgb24gdGhlIHgtYXhpcyBhbmQgbGlmZSBleHBlY3RhbmN5IG9uIHRoZSB5LWF4aXMuDQoNClRoZSBmaXJzdCBzdGFnZSBpcyB0byBzcGVjaWZ5IG91ciBkYXRhc2V0DQoNCmBgYHtyfQ0KbGlicmFyeShnZ3Bsb3QyKQ0KZ2dwbG90KGRhdGEgPSBnYXBtaW5kZXIpDQpgYGANCg0KRm9yIHRoZSBhZXN0aGV0aWNzLCBhcyBhIGJhcmUgbWluaW11bSB3ZSB3aWxsIG1hcCB0aGUgYGdkcFBlcmNhcGAgYW5kIGBsaWZlRXhwYCB0byB0aGUgeC0gYW5kIHktYXhpcyBvZiB0aGUgcGxvdC4gU29tZSBwcm9ncmVzcyBpcyBtYWRlOyB3ZSBhdCBsZWFzdCBnZXQgYXhlcw0KDQpgYGB7cn0NCmdncGxvdChkYXRhID0gZ2FwbWluZGVyLGFlcyh4PWdkcFBlcmNhcCwgeT1saWZlRXhwKSkNCmBgYA0KDQpUaGF0IGNyZWF0ZWQgdGhlIGF4ZXMsIGJ1dCB3ZSBzdGlsbCBuZWVkIHRvIGRlZmluZSBob3cgdG8gZGlzcGxheSBvdXIgcG9pbnRzIG9uIHRoZSBwbG90LiBBcyB3ZSBoYXZlIGNvbnRpbnVvdXMgZGF0YSBmb3IgYm90aCB0aGUgeC0gYW5kIHktYXhpcywgYGdlb21fcG9pbnRgIGlzIGEgZ29vZCBjaG9pY2UuDQoNCmBgYHtyfQ0KZ2dwbG90KGRhdGEgPSBnYXBtaW5kZXIsYWVzKHg9Z2RwUGVyY2FwLCB5PWxpZmVFeHApKSArIGdlb21fcG9pbnQoKQ0KYGBgDQoNCg0KDQoNClRoZSAqZ2VvbSogd2UgdXNlIHdpbGwgZGVwZW5kIG9uIHdoYXQga2luZCBvZiBkYXRhIHdlIGhhdmUgKGNvbnRpbnVvdXMsIGNhdGVnb3JpY2FsIGV0YykNCg0KLSBgZ2VvbV9wb2ludCgpYCAtIFNjYXR0ZXIgcGxvdHMNCi0gYGdlb21fbGluZSgpYCAtIExpbmUgcGxvdHMNCi0gYGdlb21fc21vb3RoKClgIC0gRml0dGVkIGxpbmUgcGxvdHMNCi0gYGdlb21fYmFyKClgIC0gQmFyIHBsb3RzDQotIGBnZW9tX2JveHBsb3QoKWAgLSBCb3hwbG90cw0KLSBgZ2VvbV9qaXR0ZXIoKWAgLSBKaXR0ZXIgdG8gcGxvdHMNCi0gYGdlb21faGlzdG9ncmFtKClgIC0gSGlzdG9ncmFtIHBsb3RzDQotIGBnZW9tX2RlbnNpdHkoKWAgLSBEZW5zaXR5IHBsb3RzDQotIGBnZW9tX3RleHQoKWAgLSBUZXh0IHRvIHBsb3RzDQotIGBnZW9tX2Vycm9yYmFyKClgIC0gRXJyb3JiYXJzIHRvIHBsb3RzDQotIGBnZW9tX3Zpb2xpbigpYCAtIFZpb2xpbiBwbG90cw0KLSBgZ2VvbV90aWxlKClgIC0gZm9yICJoZWF0bWFwIi1saWtlIHBsb3RzDQoNCg0KQm94cGxvdHMgYXJlIGNvbW1vbmx5IHVzZWQgdG8gdmlzdWFsaXNlIHRoZSBkaXN0cmlidXRpb25zIG9mIGNvbnRpbnVvdXMgZGF0YS4gV2UgaGF2ZSB0byB1c2UgYSBjYXRlZ29yaWNhbCB2YXJpYWJsZSBvbiB0aGUgeC1heGlzIHN1Y2ggYXMgYGNvbnRpbmVudGAgb3IgYGNvdW50cnlgIChub3QgYWR2aXNhYmxlIGluIHRoaXMgY2FzZSBhcyB0aGVyZSBhcmUgdG9vIG1hbnkgZGlmZmVyZW50IHZhbHVlcykuIA0KDQpUaGUgb3JkZXIgb2YgdGhlIGJveGVzIGFsb25nIHRoZSB4LWF4aXMgaXMgZGljdGF0ZWQgYnkgdGhlIG9yZGVyIG9mIGNhdGVnb3JpZXMgaW4gdGhlIGZhY3Rvcjsgd2l0aCB0aGUgZGVmYXVsdCBmb3IgbmFtZXMgYmVpbmcgYWxwaGFiZXRpY2FsIG9yZGVyLg0KDQpgYGB7cn0NCmdncGxvdChnYXBtaW5kZXIsIGFlcyh4ID0gY29udGluZW50LCB5PWdkcFBlcmNhcCkpICsgZ2VvbV9ib3hwbG90KCkNCmBgYA0KDQoNCmBgYHtyfQ0KZ2dwbG90KGdhcG1pbmRlciwgYWVzKHggPSBnZHBQZXJjYXApKSArIGdlb21faGlzdG9ncmFtKCkNCmBgYA0KDQpQcm9kdWNpbmcgYSBiYXJwbG90IG9mIGNvdW50cyBvbmx5IHJlcXVpcmVzIGFuIGB4YCB2YXJpYWJsZS4gVGhlIGNvdW50cyB3aWxsIGJlIGdlbmVyYXRlZCBieSBSLiANCg0KYGBge3J9DQpnZ3Bsb3QoZ2FwbWluZGVyLCBhZXMoeD1jb250aW5lbnQpKSArIGdlb21fYmFyKCkNCmBgYA0KDQpUaGUgaGVpZ2h0IG9mIHRoZSBiYXJzIGNhbiBhbHNvIGJlIG1hcHBlZCBkaXJlY3RseSB0byBudW1lcmljIHZhcmlhYmxlcyBpbiB0aGUgZGF0YSBmcmFtZSBpZiB0aGUgYGdlb21fY29sYCBmdW5jdGlvbiBpcyB1c2VkIGluc3RlYWQuIA0KDQpJbiB0aGUgYmVsb3cgcGxvdCB0aGUgYXhpcyBsYWJlbHMgd2lsbCBiZSBtZXNzeSBhbmQgZGlmZmljdWx0IHRvIHJlYWQuIFRoaXMgaXMgc29tZXRoaW5nIHRoYXQgY2FuIGJlIGN1c3RvbWlzZWQgd2l0aCBzb21lIG9mIHRoZSBgZ2dwbG90MmAgb3B0aW9ucyB3ZSB3aWxsIGV4cGxvcmUgbGF0ZXIuDQoNCmBgYHtyfQ0KZ2FwbWluZGVyMjAwMiA8LSBmaWx0ZXIoZ2FwbWluZGVyLCB5ZWFyPT0yMDAyLGNvbnRpbmVudD09IkFtZXJpY2FzIikNCmdncGxvdChnYXBtaW5kZXIyMDAyLCBhZXMoeD1jb3VudHJ5LHk9Z2RwUGVyY2FwKSkgKyBnZW9tX2NvbCgpDQpgYGANCg0KV2hlcmUgYXBwcm9wcmlhdGUsIHdlIGNhbiBhZGQgbXVsdGlwbGUgbGF5ZXJzIG9mIGBnZW9tYHMgdG8gdGhlIHBsb3QuIEZvciBpbnN0YW5jZSwgYSBjcml0aWNpc20gb2YgdGhlIGJveHBsb3QgaXMgdGhhdCBpdCBkb2VzIG5vdCBzaG93IGFsbCB0aGUgZGF0YS4gV2UgY2FuIHJlY3RpZnkgdGhpcyBieSBvdmVybGF5aW5nIHRoZSBpbmRpdmlkdWFsIHBvaW50cy4NCg0KYGBge3J9DQpnZ3Bsb3QoZ2FwbWluZGVyLCBhZXMoeCA9IGNvbnRpbmVudCwgeT1nZHBQZXJjYXApKSArIGdlb21fYm94cGxvdCgpICsgZ2VvbV9wb2ludCgpDQpgYGANCg0KYGBge3J9DQpnZ3Bsb3QoZ2FwbWluZGVyLCBhZXMoeCA9IGNvbnRpbmVudCwgeT1nZHBQZXJjYXApKSArIGdlb21fYm94cGxvdCgpICsgZ2VvbV9qaXR0ZXIod2lkdGg9MC4xKQ0KYGBgDQoNCg0KKioqKioqDQoqKioqKioNCioqKioqKg0KDQojIyMgRXhlcmNpc2VzDQoNCjxkaXYgY2xhc3M9ImV4ZXJjaXNlIj4NCi0gVGhlIHZpb2xpbiBwbG90IGlzIGEgcG9wdWxhciBhbHRlcm5hdGl2ZSB0byB0aGUgYm94cGxvdC4gQ3JlYXRlIGEgdmlvbGluIHBsb3Qgd2l0aCBgZ2VvbV92aW9saW5gIHRvIHZpc3VhbGlzZSB0aGUgZGlmZmVyZW5jZXMgaW4gR0RQIGJldHdlZW4gZGlmZmVyZW50IGNvbnRpbmVudHMuDQotIENyZWF0ZSBhIHN1YnNldCBvZiB0aGUgYGdhcG1pbmRlcmAgZGF0YSBmcmFtZSBjb250YWluaW5nIGp1c3QgdGhlIHJvd3MgZm9yIHlvdXIgY291bnRyeSBvZiBiaXJ0aA0KLSBIYXMgdGhlcmUgYmVlbiBhbiBpbmNyZWFzZSBpbiBsaWZlIGV4cGVjdGFuY3kgb3ZlciB0aW1lPw0KICAgICsgdmlzdWFsaXNlIHRoZSB0cmVuZCB1c2luZyBhIHNjYXR0ZXIgcGxvdCAoYGdlb21fcG9pbnRgKSwgbGluZSBncmFwaCAoYGdlb21fbGluZWApIG9yIHNtb290aGVkIGxpbmUgKGBnZW9tX3Ntb290aGApLg0KLSBXaGF0IGhhcHBlbnMgd2hlbiB5b3UgbW9kaWZ5IHRoZSBgZ2VvbV9ib3hwbG90YCBleGFtcGxlIHRvIGNvbXBhcmUgdGhlIGdkcCBkaXN0cmlidXRpb25zIGZvciBkaWZmZXJlbnQgeWVhcnM/DQogICAgKyBMb29rIGF0IHRoZSBtZXNzYWdlIGBnZ3Bsb3QyYCBwcmludHMgYWJvdmUgdGhlIHBsb3QgYW5kIHRyeSB0byBtb2RpZnkgdGhlIGNvZGUgdG8gZ2l2ZSBhIHNlcGFyYXRlIGJveHBsb3QgZm9yIGVhY2ggeWVhcg0KPC9kaXY+DQoNCioqKioqKg0KKioqKioqDQoqKioqKioNCg0KDQpgYGB7cn0NCg0KYGBgDQoNCg0KDQoNCg0KQXMgd2UgaGF2ZSBzZWVuIGFscmVhZHksIGBnZ3Bsb3RgIG9mZmVycyBhbiBpbnRlcmZhY2UgdG8gY3JlYXRlIG1hbnkgcG9wdWxhciBwbG90IHR5cGVzLiBJdCBpcyB1cCB0byB0aGUgdXNlciB0byBkZWNpZGUgd2hhdCB0aGUgYmVzdCB3YXkgdG8gdmlzdWFsaXNlIHRoZSBkYXRhLg0KDQoNCg0KDQojIyBDdXN0b21pc2luZyB0aGUgcGxvdCBhcHBlYXJhbmNlDQoNCk91ciBwbG90cyBhcmUgYSBiaXQgZHJlYXJ5IGF0IHRoZSBtb21lbnQsIGJ1dCBvbmUgd2F5IHRvIGFkZCBjb2xvdXIgaXMgdG8gYWRkIGEgYGNvbGAgYXJndW1lbnQgdG8gdGhlIGBnZW9tX3BvaW50YCBmdW5jdGlvbi4gVGhlIHZhbHVlIGNhbiBiZSBhbnkgb2YgdGhlIHByZS1kZWZpbmVkIGNvbG91ciBuYW1lcyBpbiBSLiBUaGVzZSBhcmUgZGlzcGxheWVkIGluIHRoaXMgW2hhbmR5IG9ubGluZSByZWZlcmVuY2VdKGh0dHA6Ly93d3cuc3RhdC5jb2x1bWJpYS5lZHUvfnR6aGVuZy9maWxlcy9SY29sb3IucGRmKS4gKlIqZWQsICpHKnJlZW4sICpCKmx1ZSBvZiAqSGV4KiB2YWx1ZXMgY2FuIGFsc28gYmUgZ2l2ZW4uDQoNCmBgYHtyfQ0KZ2dwbG90KGdhcG1pbmRlciwgYWVzKHggPSBnZHBQZXJjYXAsIHk9bGlmZUV4cCkpICsgZ2VvbV9wb2ludChjb2w9InJlZCIpDQpgYGANCg0KSG93ZXZlciwgYSBwb3dlcmZ1bCBmZWF0dXJlIG9mIGBnZ3Bsb3QyYCBpcyB0aGF0IGNvbG91cnMgYXJlIHRyZWF0ZWQgYXMgYWVzdGhldGljcyBvZiB0aGUgcGxvdC4gSW4gb3RoZXIgd29yZHMgd2UgY2FuIHVzZSBhIGNvbHVtbiBpbiBvdXIgZGF0YXNldC4NCg0KTGV0J3Mgc2F5IHRoYXQgd2Ugd2FudCBwb2ludHMgb24gb3VyIHBsb3QgdG8gYmUgY29sb3VyZWQgYWNjb3JkaW5nIHRvIGNvbnRpbmVudC4gV2UgYWRkIGFuIGV4dHJhIGFyZ3VtZW50IHRvIHRoZSBkZWZpbml0aW9uIG9mIGFlc3RoZXRpY3MgdG8gZGVmaW5lIHRoZSBtYXBwaW5nLiBgZ2dwbG90MmAgd2lsbCBldmVuIGRlY2lkZSBvbiBjb2xvdXJzIGFuZCBjcmVhdGUgYSBsZWdlbmQgZm9yIHVzLg0KDQpgYGB7cn0NCmdncGxvdChnYXBtaW5kZXIsIGFlcyh4ID0gZ2RwUGVyY2FwLCB5PWxpZmVFeHAsY29sPWNvbnRpbmVudCkpICsgZ2VvbV9wb2ludCgpDQpgYGANCg0KSXQgd2lsbCBldmVuIGNob29zZSBhIGNvbnRpbnVvdXMgb3IgZGlzY3JldGUgY29sb3VyIHNjYWxlIGJhc2VkIG9uIHRoZSBkYXRhIHR5cGUuIFdlIGhhdmUgYWxyZWFkeSBzZWVuIHRoYXQgYGdncGxvdDJgIGlzIHRyZWF0IG91ciBgeWVhcmAgY29sdW1uIGFzIG51bWVyaWNhbCBkYXRhOyB3aGljaCBpcyBwcm9iYWJseSBub3QgdmVyeSB1c2VmdWwgZm9yIHZpc3VhbGlzYXRpb24uDQoNCmBgYHtyfQ0KZ2dwbG90KGdhcG1pbmRlciwgYWVzKHggPSBnZHBQZXJjYXAsIHk9bGlmZUV4cCxjb2w9eWVhcikpICsgZ2VvbV9wb2ludCgpDQpgYGANCg0KV2UgY2FuIGZvcmNlIGBnZ3Bsb3QyYCB0byB0cmVhdCBgeWVhcmAgYXMgY2F0ZWdvcmljYWwgZGF0YSBieSB1c2luZyBgYXMuZmFjdG9yYCB3aGVuIGNyZWF0aW5nIHRoZSBhZXN0aGV0aWNzLg0KDQpgYGB7cn0NCmdncGxvdChnYXBtaW5kZXIsIGFlcyh4ID0gZ2RwUGVyY2FwLCB5PWxpZmVFeHAsY29sPWFzLmZhY3Rvcih5ZWFyKSkpICsgZ2VvbV9wb2ludCgpDQpgYGANCg0KV2hlbiB1c2VkIGluIHRoZSBjb25zdHJ1Y3Rpb24gb2YgYSBib3hwbG90LCB0aGUgYGNvbGAgYXJndW1lbnQgd2lsbCBjaGFuZ2UgdGhlIGNvbG91ciBvZiB0aGUgbGluZXMuIFRvIGNoYW5nZSB0aGUgY29sb3VyIG9mIHRoZSBib3hlcyB3ZSBoYXZlIHRvIHVzZSBgZmlsbGAuDQoNCmBgYHtyfQ0KZ2dwbG90KGdhcG1pbmRlciwgYWVzKHggPSBjb250aW5lbnQsIHk9Z2RwUGVyY2FwLGZpbGw9Y29udGluZW50KSkgKyBnZW9tX2JveHBsb3QoKQ0KYGBgDQoNCg0KVGhlIHNoYXBlIGFuZCBzaXplIG9mIHBvaW50cyBjYW4gYWxzbyBiZSBtYXBwZWQgZnJvbSB0aGUgZGF0YS4gSG93ZXZlciwgaXQgaXMgZWFzeSB0byBnZXQgY2FycmllZCBhd2F5Lg0KDQpgYGB7cn0NCmdncGxvdChnYXBtaW5kZXIsIGFlcyh4ID0gZ2RwUGVyY2FwLCB5PWxpZmVFeHAsc2hhcGU9Y29udGluZW50LHNpemU9cG9wKSkgKyBnZW9tX3BvaW50KCkNCmBgYA0KDQpTY2FsZXMgYW5kIHRoZWlyIGxlZ2VuZHMgaGF2ZSBzbyBmYXIgYmVlbiBoYW5kbGVkIHVzaW5nIGBnZ3Bsb3QyYCBkZWZhdWx0cy4gYGdncGxvdDJgIG9mZmVycyBmdW5jdGlvbmFsaXR5IHRvIGhhdmUgZmluZXIgY29udHJvbCBvdmVyIHNjYWxlcyBhbmQgbGVnZW5kcyB1c2luZyB0aGUgc2NhbGUgbWV0aG9kcy4NCg0KU2NhbGUgbWV0aG9kcyBhcmUgZGl2aWRlZCBpbnRvIGZ1bmN0aW9ucyBieSBjb21iaW5hdGlvbnMgb2YNCg0KLSB0aGUgYWVzdGhldGljcyB0aGV5IGNvbnRyb2wuDQoNCi0gdGhlIHR5cGUgb2YgZGF0YSBtYXBwZWQgdG8gc2NhbGUuDQoNCmBzY2FsZV9gKmFlc3RoZXRpYypfKnR5cGUqDQoNClRyeSB0eXBpbmcgaW4gYHNjYWxlX2AgdGhlbiB0YWIgdG8gYXV0b2NvbXBsZXRlLiBUaGlzIHdpbGwgcHJvdmlkZSBzb21lIGV4YW1wbGVzIG9mIHRoZSBzY2FsZSBmdW5jdGlvbnMgYXZhaWxhYmxlIGluIGBnZ3Bsb3QyYC4NCg0KQWx0aG91Z2ggZGlmZmVyZW50IHNjYWxlIGZ1bmN0aW9ucyBhY2NlcHQgc29tZSB2YXJpZXR5IGluIHRoZWlyIGFyZ3VtZW50cywgY29tbW9uIGFyZ3VtZW50cyB0byBzY2FsZSBmdW5jdGlvbnMgaW5jbHVkZSAtDQoNCi0gbmFtZSAtIFRoZSBheGlzIG9yIGxlZ2VuZCB0aXRsZQ0KDQotIGxpbWl0cyAtIE1pbmltdW0gYW5kIG1heGltdW0gb2YgdGhlIHNjYWxlDQoNCi0gYnJlYWtzIC0gTGFiZWwvdGljayBwb3NpdGlvbnMgYWxvbmcgYW4gYXhpcw0KDQotIGxhYmVscyAtIExhYmVsIG5hbWVzIGF0IGVhY2ggYnJlYWsNCg0KLSB2YWx1ZXMgLSB0aGUgc2V0IG9mIGFlc3RoZXRpYyB2YWx1ZXMgdG8gbWFwIGRhdGEgdmFsdWVzDQoNCldlIGNhbiBjaG9vc2Ugc3BlY2lmaWMgY29sb3VyIHBhbGV0dGVzLCBzdWNoIGFzIHRob3NlIHByb3ZpZGVkIGJ5IHRoZSBgUkNvbG9yQnJld2VyYCBwYWNrYWdlLiBUaGlzIHBhY2thZ2UgcHJvdmlkZXMgcGFsZXR0ZXMgZm9yIGRpZmZlcmVudCB0eXBlcyBvZiBzY2FsZSAoc2VxdWVudGlhbCwgZGl2ZXJnaW5nLCBxdWFsaXRhdGl2ZSkuDQoNCg0KYGBge3J9DQpsaWJyYXJ5KFJDb2xvckJyZXdlcikNCmRpc3BsYXkuYnJld2VyLmFsbChjb2xvcmJsaW5kRnJpZW5kbHkgPSBUUlVFKQ0KYGBgDQoNCjxkaXYgY2xhc3M9Indhcm5pbmciPg0KKipXaGVuIGNyZWF0aW5nIGEgcGxvdCwgYWx3YXlzIGNoZWNrIHRoYXQgdGhlIGNvbG91ciBzY2hlbWUgaXMgYXBwcm9wcmlhdGUgZm9yIHBlb3BsZSB3aXRoIHZhcmlvdXMgZm9ybXMgb2YgY29sb3VyLWJsaW5kbmVzcyoqDQo8L2Rpdj4NCg0KV2hlbiBleHBlcmltZW50aW5nIHdpdGggY29sb3VyIHBhbGV0dGVzIGFuZCBsYWJlbHMsIGl0IGlzIHVzZWZ1bCB0byBzYXZlIHRoZSBwbG90IGFzIGFuIG9iamVjdA0KYGBge3J9DQpwIDwtIGdncGxvdChnYXBtaW5kZXIsIGFlcyh4ID0gZ2RwUGVyY2FwLCB5PWxpZmVFeHAsY29sPWNvbnRpbmVudCkpICsgZ2VvbV9wb2ludCgpDQpgYGANCg0KDQpgYGB7cn0NCiMgV2UgY2FuIGFsc28gY2hhbmdlIHRoZSB0ZXh0IGRpc3BsYXllZCBhYm92ZSB0aGUgbGVnZW5kIHdpdGggdGhlIG5hbWUgcGFyYW1ldGVyLg0KcCArIHNjYWxlX2NvbG9yX2JyZXdlcihuYW1lPSJDb250aW5lbnQiLCBwYWxldHRlID0gIlNldDIiKQ0KYGBgDQoNCk9yIHdlIGNhbiBldmVuIHNwZWNpZnkgb3VyIG93biBjb2xvdXJzOyBzdWNoIGFzIFRoZSBVbml2ZXJzaXR5IG9mIFNoZWZmaWVsZCBicmFuZGluZyBjb2xvdXJzDQoNCmBgYHtyfQ0KbXlfcGFsIDwtIGMocmdiKDAsMTU5LDIxOCxtYXhDb2xvclZhbHVlID0gMjU1KSwNCiAgICAgICAgICAgIHJnYigzMSwyMCw5MyxtYXhDb2xvclZhbHVlID0gMjU1KSwNCiAgICAgICAgICAgIHJnYigyNDksMjI3LDAsbWF4Q29sb3JWYWx1ZSA9IDI1NSksDQogICAgICAgICAgICByZ2IoMCwxNTUsNzIsbWF4Q29sb3JWYWx1ZSA9IDI1NSksDQogICAgICAgICAgICByZ2IoMTkwLDIxNCwwLG1heENvbG9yVmFsdWUgPSAyNTUpKQ0KcCArIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9bXlfcGFsKQ0KDQpgYGANCg0KDQoNClZhcmlvdXMgbGFiZWxzIGNhbiBiZSBtb2RpZmllZCB1c2luZyB0aGUgYGxhYnNgIGZ1bmN0aW9uLg0KDQpgYGB7cn0NCnAgKyBsYWJzKHg9IldlYWx0aCIseT0iTGlmZSBFeHBlY3RhbmN5Iix0aXRsZT0iUmVsYXRpb25zaGlwIGJldHdlZW4gV2VhbHRoIGFuZCBMaWZlIEV4cGVjdGFuY3kiKQ0KYGBgDQoNCldlIGNhbiBhbHNvIG1vZGlmeSB0aGUgeC0gYW5kIHktIGxpbWl0cyBvZiB0aGUgcGxvdCBzbyB0aGF0IGFueSBvdXRsaWVycyBhcmUgbm90IHNob3duLiBgZ2dwbG90MmAgd2lsbCBnaXZlIGEgd2FybmluZyB0aGF0IHNvbWUgcG9pbnRzIGFyZSBleGNsdWRlZC4NCg0KYGBge3J9DQpwICsgeGxpbSgwLDYwMDAwKQ0KYGBgDQoNClNhdmluZyBpcyBzdXBwb3J0ZWQgYnkgdGhlIGBnZ3NhdmVgIGZ1bmN0aW9uLiBBIHZhcmlldHkgb2YgZmlsZSBmb3JtYXRzIGFyZSBzdXBwb3J0ZWQgKGAucG5nYCwgYC5wZGZgLCBgLnRpZmZgLCBldGMpIGFuZCB0aGUgZm9ybWF0IHVzZWQgaXMgZGV0ZXJtaW5lZCBmcm9tIHRoZSBleHRlbnNpb24gZ2l2ZW4gaW4gdGhlIGBmaWxlYCBhcmd1bWVudC4gVGhlIGhlaWdodCwgd2lkdGggYW5kIHJlc29sdXRpb24gY2FuIGFsc28gYmUgY29uZmlndXJlZC4gU2VlIHRoZSBoZWxwIG9uIGBnZ3NhdmVgIChgP2dnc2F2ZWApIGZvciBtb3JlIGluZm9ybWF0aW9uLg0KDQpgYGB7cn0NCmdnc2F2ZShwLCBmaWxlPSJteV9nZ3Bsb3QucG5nIikNCmBgYA0KDQpNb3N0IGFzcGVjdHMgb2YgdGhlIHBsb3QgY2FuIGJlIG1vZGlmaWVkIGZyb20gdGhlIGJhY2tncm91bmQgY29sb3VyIHRvIHRoZSBncmlkIHNpemVzIGFuZCBmb250LiBTZXZlcmFsIHByZS1kZWZpbmVkICJ0aGVtZXMiIGV4aXN0IGFuZCB3ZSBjYW4gbW9kaWZ5IHRoZSBhcHBlYXJhbmNlIG9mIHRoZSB3aG9sZSBwbG90IHVzaW5nIGEgYHRoZW1lXy4uYCBmdW5jdGlvbi4NCg0KYGBge3J9DQpwICsgdGhlbWVfYncoKQ0KYGBgDQoNCk1vcmUgdGhlbWVzIGFyZSBzdXBwb3J0ZWQgYnkgdGhlIGBnZ3RoZW1lc2AgcGFja2FnZS4gWW91IGNhbiBtYWtlIHlvdXIgcGxvdHMgbG9vayBsaWtlIHRoZSBFY29ub21pc3QsIFdhbGwgU3RyZWV0IEpvdXJuYWwgb3IgRXhjZWwgKCoqYnV0IHBsZWFzZSBkb24ndCBkbyB0aGlzISoqKQ0KDQoqKioqKioNCioqKioqKg0KKioqKioqDQoNCg0KIyMgRmFjZXRzDQoNCk9uZSB2ZXJ5IHVzZWZ1bCBmZWF0dXJlIG9mIGBnZ3Bsb3QyYCBpcyBmYWNldGluZy4gVGhpcyBhbGxvd3MgeW91IHRvIHByb2R1Y2UgcGxvdHMgc3Vic2V0IGJ5IHZhcmlhYmxlcyBpbiB5b3VyIGRhdGEuIEluIHRoZSBzY2F0dGVyIHBsb3QgYWJvdmUsIGl0IHdhcyBxdWl0ZSBkaWZmaWN1bHQgdG8gc2VlIGlmIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBnZHAgYW5kIGxpZmUgZXhwZWN0YW5jeSB3YXMgdGhlIHNhbWUgZm9yIGVhY2ggY29udGluZW50LiBUbyBvdmVyY29tZSB0aGlzLCB3ZSB3b3VsZCBsaWtlIGEgc2VlIGEgc2VwYXJhdGUgcGxvdCBmb3IgZWFjaCBjb250aW5lbnQuDQoNClRvIGZhY2V0IG91ciBkYXRhIGludG8gbXVsdGlwbGUgcGxvdHMgd2UgY2FuIHVzZSB0aGUgYGZhY2V0X3dyYXBgICgxIHZhcmlhYmxlKSBvciBgZmFjZXRfZ3JpZGAgKDIgdmFyaWFibGVzKSBmdW5jdGlvbnMgYW5kIHNwZWNpZnkgdGhlIHZhcmlhYmxlKHMpIHdlIHNwbGl0IGJ5LiANCg0KYGBge3J9DQpwICsgZmFjZXRfd3JhcCh+Y29udGluZW50KQ0KDQpgYGANCg0KVGhlIGBmYWNldF9ncmlkYCBmdW5jdGlvbiB3aWxsIGNyZWF0ZSBhIGdyaWQtbGlrZSBwbG90IHdpdGggb25lIHZhcmlhYmxlIG9uIHRoZSB4LWF4aXMgYW5kIGFub3RoZXIgb24gdGhlIHktYXhpcy4NCg0KYGBge3IgZmlnLndpZHRoPTEyfQ0KcCArIGZhY2V0X2dyaWQoY29udGluZW50fnllYXIpDQpgYGANCg0KDQpUaGUgcHJldmlvdXMgcGxvdCB3YXMgYSBiaXQgbWVzc3kgYXMgaXQgY29udGFpbmVkIGFsbCBjb21iaW5hdGlvbnMgb2YgeWVhciBhbmQgY29udGluZW50LiBMZXQncyBzdXBwb3NlIHdlIHdhbnQgb3VyIGFuYWx5c2lzIHRvIGJlIGEgYml0IG1vcmUgZm9jdXNlZCBhbmQgZGlzcmVnYXJkIGNvdW50cmllcyBpbiBPY2VhbmlhIChhcyB0aGVyZSBhcmUgb25seSAyIGluIG91ciBkYXRhc2V0KSBhbmQgbWF5YmUgeWVhcnMgYmV0d2VlbiAxOTk3IGFuZCAyMDAyLiANCg0KDQpXZSBzaG91bGQga25vdyBob3cgdG8gcmVzdHJpY3QgdGhlIHJvd3MgZnJvbSB0aGUgYGdhcG1pbmRlcmAgZGF0YXNldCB1c2luZyB0aGUgYGZpbHRlcmAgZnVuY3Rpb24uIEluc3RlYWQgb2YgZmlsdGVyaW5nIHRoZSBkYXRhLCBjcmVhdGluZyBhIG5ldyBkYXRhIGZyYW1lIGFuZCBjb25zdHJ1Y3RpbmcgdGhlIGRhdGEgZnJhbWUgZnJvbSB0aGVzZSBuZXcgZGF0YSB3ZSBjYW4gdXNlIHRoZWAgJT4lYCBvcGVyYXRvciB0byBjcmVhdGUgdGhlIGRhdGEgZnJhbWUgb24gdGhlIGZseSBhbmQgcGFzcyBkaXJlY3RseSB0byBgZ2dwbG90YC4gVGh1cyB3ZSBkb24ndCBoYXZlIHRvIHNhdmUgYSBuZXcgZGF0YSBmcmFtZSBvciBhbHRlciB0aGUgb3JpZ2luYWwgZGF0YS4NCg0KDQpgYGB7ciBmaWcud2lkdGg9MTJ9DQpmaWx0ZXIoZ2FwbWluZGVyLCBjb250aW5lbnQhPSJPY2VhbmlhIiwgeWVhciAlaW4lIGMoMTk5NywyMDAyLDIwMDcpKSAlPiUgDQogIGdncGxvdChhZXMoeCA9IGdkcFBlcmNhcCwgeT1saWZlRXhwLGNvbD1jb250aW5lbnQpKSArIGdlb21fcG9pbnQoKSArIGZhY2V0X2dyaWQoY29udGluZW50fnllYXIpDQpgYGANCg0KDQojIFN1bW1hcmlzaW5nIGFuZCBncm91cGluZyB3aXRoIGRwbHlyDQoNCg0KDQpUaGUgYHN1bW1hcmlzZWAgZnVuY3Rpb24gY2FuIHRha2UgYW55IFIgZnVuY3Rpb24gdGhhdCB0YWtlcyBhIHZlY3RvciBvZiB2YWx1ZXMgKGkuZS4gYSBjb2x1bW4gZnJvbSBhIGRhdGEgZnJhbWUpIGFuZCByZXR1cm5zIGEgc2luZ2xlIHZhbHVlLiBTb21lIG9mIHRoZSBtb3JlIHVzZWZ1bCBmdW5jdGlvbnMgaW5jbHVkZToNCg0KLSBgbWluYCBtaW5pbXVtIHZhbHVlDQotIGBtYXhgIG1heGltdW0gdmFsdWUNCi0gYHN1bWAgc3VtIG9mIHZhbHVlcw0KLSBgbWVhbmAgbWVhbiB2YWx1ZQ0KLSBgc2RgIHN0YW5kYXJkIGRldmlhdGlvbg0KLSBgbWVkaWFuYCBtZWRpYW4gdmFsdWUNCi0gYElRUmAgdGhlIGludGVycXVhcnRpbGUgcmFuZ2UNCi0gYG5fZGlzdGluY3RgIHRoZSBudW1iZXIgb2YgZGlzdGluY3QgdmFsdWVzDQotIGBuYCB0aGUgbnVtYmVyIG9mIG9ic2VydmF0aW9ucyAoTm90ZTogdGhpcyBpcyBhIHNwZWNpYWwgZnVuY3Rpb24gdGhhdCBkb2VzbuKAmXQgdGFrZSBhIHZlY3RvciBhcmd1bWVudCwgaS5lLiBjb2x1bW4pDQoNCg0KYGBge3J9DQpsaWJyYXJ5KGRwbHlyKQ0Kc3VtbWFyaXNlKGdhcG1pbmRlciwgbWluKGxpZmVFeHApLCBtYXgoZ2RwUGVyY2FwKSwgbWVhbihwb3ApKQ0KYGBgDQoNCkl0IGlzIGFsc28gcG9zc2libGUgdG8gc3VtbWFyaXNlIHVzaW5nIGEgZnVuY3Rpb24gdGhhdCB0YWtlcyBtb3JlIHRoYW4gb25lIHZhbHVlLCBpLmUuIGZyb20gbXVsdGlwbGUgY29sdW1ucy4gRm9yIGV4YW1wbGUsIHdlIGNvdWxkIGNvbXB1dGUgdGhlIGNvcnJlbGF0aW9uIGJldHdlZW4geWVhciBhbmQgbGlmZSBleHBlY3RhbmN5LiBIZXJlIHdlIGFsc28gYXNzaWduIG5hbWVzIHRvIHRoZSB0YWJsZSB0aGF0IGlzIHByb2R1Y2VkLg0KDQpgYGB7cn0NCmdhcG1pbmRlciAlPiUgDQpzdW1tYXJpc2UoTWluTGlmZUV4cGVjdGFuY3kgPSBtaW4obGlmZUV4cCksIA0KICAgICAgICAgIE1heGltdW1HRFAgPSBtYXgoZ2RwUGVyY2FwKSwgDQogICAgICAgICAgQXZlcmFnZVBvcCA9IG1lYW4ocG9wKSwgDQogICAgICAgICAgQ29ycmVsYXRpb24gPSBjb3IoeWVhciwgbGlmZUV4cCkpDQpgYGANCg0KSG93ZXZlciwgaXQgaXMgbm90IHBhcnRpY3VsYXJseSB1c2VmdWwgdG8gY2FsY3VsYXRlIHN1Y2ggdmFsdWVzIGZyb20gdGhlIGVudGlyZSB0YWJsZSBhcyB3ZSBoYXZlIGRpZmZlcmVudCBjb250aW5lbnRzIGFuZCB5ZWFycy4gVGhlIGBncm91cF9ieWAgZnVuY3Rpb24gYWxsb3dzIHVzIHRvIHNwbGl0IHRoZSB0YWJsZSBpbnRvIGRpZmZlcmVudCBjYXRlZ29yaWVzLCBhbmQgY29tcHV0ZSBzdW1tYXJ5IHN0YXRpc3RpY3MgZm9yIGVhY2ggeWVhciAoZm9yIGV4YW1wbGUpLg0KDQpgYGB7cn0NCmdhcG1pbmRlciAlPiUgDQogICAgZ3JvdXBfYnkoeWVhcikgJT4lIA0KICAgIHN1bW1hcmlzZShNaW5MaWZlRXhwZWN0YW5jeSA9IG1pbihsaWZlRXhwKSwgDQogICAgICAgICAgICAgIE1heGltdW1HRFAgPSBtYXgoZ2RwUGVyY2FwKSwgDQogICAgICAgICAgICAgIEF2ZXJhZ2VQb3AgPSBtZWFuKHBvcCkpDQpgYGANCg0KSWYgd2Ugd2FudCB0byBzdW1tYXJpc2Ugc2V2ZXJhbCBjb2x1bW5zIHdlIGNhbiB1c2UgdGhlIGNvbnZlbmllbnQgYHN1bW1hcmlzZV9hbGxgIGZ1bmN0aW9uLiBIb3dldmVyLCB0aGlzIHdpbGwgcmV0dXJuIGBOQWAgdmFsdWVzIGZvciBjb2x1bW5zIHRoYXQgZG8gbm90IGNvbnRhaW4gbnVtZXJpYyB2YWx1ZXMuDQoNCmBgYHtyIHdhcm5pbmc9RkFMU0V9DQpnYXBtaW5kZXIgJT4lIA0KICBncm91cF9ieShjb250aW5lbnQseWVhcikgJT4lIA0KICBzdW1tYXJpc2VfYWxsKG1lYW4pDQpgYGANCg0KDQoNClRoZSBuaWNlIHRoaW5nIGFib3V0IGBzdW1tYXJpc2VgIGlzIHRoYXQgaXQgY2FuIGZvbGxvd2VkIHVwIGJ5IGFueSBvZiB0aGUgb3RoZXIgYGRwbHlyYCB2ZXJicyB0aGF0IHdlIGhhdmUgbWV0IHNvIGZhciAoYHNlbGVjdGAsIGBmaWx0ZXJgLCBgYXJyYW5nZWAuLmV0YykuIEFzIHRoZSBgY291bnRyeWAgY29sdW1uIG9mIHRoZSBwcmV2aW91cyBvdXRwdXQgY29udGFpbmluZyBtaXNzaW5nIHZhbHVlcyB3ZSBjYW4gZXhjbHVkZSBpdCBmcm9tIGZ1cnRoZXIgcHJvY2Vzc2luZy4NCg0KYGBge3Igd2FybmluZz1GQUxTRX0NCmdhcG1pbmRlciAlPiUgDQogIGdyb3VwX2J5KGNvbnRpbmVudCx5ZWFyKSAlPiUgDQogIHN1bW1hcmlzZV9hbGwobWVhbikgJT4lIA0KICBzZWxlY3QoLWNvdW50cnkpDQpgYGANCg0KUmV0dXJuaW5nIHRvIHRoZSBjb3JyZWxhdGlvbiBiZXR3ZWVuIGxpZmUgZXhwZWN0YW5jeSBhbmQgeWVhciwgd2UgY2FuIHN1bW1hcmlzZSBhcyBmb2xsb3dzOi0NCg0KYGBge3J9DQpnYXBtaW5kZXIgJT4lICAgICANCiAgICBncm91cF9ieShjb3VudHJ5KSAlPiUgDQogICAgc3VtbWFyaXNlKENvcnJlbGF0aW9uID0gY29yKHllYXIgLCBsaWZlRXhwKSkNCmBgYA0KDQpXZSBjYW4gdGhlbiBhcnJhbmdlIHRoZSB0YWJsZSBieSB0aGUgY29ycmVsYXRpb24gdG8gc2VlIHdoaWNoIGNvdW50cmllcyBoYXZlIHRoZSBsb3dlc3QgY29ycmVsYXRpb24NCg0KYGBge3J9DQpnYXBtaW5kZXIgJT4lICAgICAgDQogICAgZ3JvdXBfYnkoY291bnRyeSkgJT4lIA0KICAgIHN1bW1hcmlzZShDb3JyZWxhdGlvbiA9IGNvcih5ZWFyICwgbGlmZUV4cCkpICU+JSANCiAgICBhcnJhbmdlKENvcnJlbGF0aW9uKQ0KYGBgDQoNCldlIGNhbiBmaWx0ZXIgdGhlIHJlc3VsdHMgdG8gZmluZCBvYnNlcnZhdGlvbnMgb2YgaW50ZXJlc3QNCg0KYGBge3J9DQpnYXBtaW5kZXIgJT4lICAgICAgDQogICAgZ3JvdXBfYnkoY291bnRyeSkgJT4lIA0KICAgIHN1bW1hcmlzZShDb3JyZWxhdGlvbiA9IGNvcih5ZWFyICwgbGlmZUV4cCkpICU+JSANCiAgICBmaWx0ZXIoQ29ycmVsYXRpb24gPCAwKQ0KYGBgDQoNClRoZSBjb3VudHJpZXMgd2UgaWRlbnRpZnkgY291bGQgdGhlbiBiZSB1c2VkIGFzIHRoZSBiYXNpcyBmb3IgYSBwbG90Lg0KDQpgYGB7cn0NCmxpYnJhcnkoZ2dwbG90MikNCmZpbHRlcihnYXBtaW5kZXIsIGNvdW50cnkgJWluJSBjKCJSd2FuZGEiLCJaYW1iaWEiLCJaaW1iYWJ3ZSIpKSAlPiUgDQogIGdncGxvdChhZXMoeD15ZWFyLCB5PWxpZmVFeHAsY29sPWNvdW50cnkpKSArIGdlb21fbGluZSgpDQpgYGAgDQoNCioqKioqKg0KKioqKioqDQoqKioqKioNCiMjIyBFeGVyY2lzZSANCjxkaXYgY2xhc3M9ImV4ZXJjaXNlIj4NCi0gU3VtbWFyaXNlIHRoZSBgZ2FwbWluZGVyYCBkYXRhIGluIGFuIGFwcHJvcHJpYXRlIG1hbm5lciB0byBwcm9kdWNlIGEgcGxvdCB0byBzaG93IHRoZSBjaGFuZ2UgaW4gYXZlcmFnZSBgZ2RwUGVyY2FwYCBmb3IgZWFjaCBjb250aW5lbnQgb3ZlciB0aW1lLg0KLSBzZWUgYmVsb3cgZm9yIGEgc3VnZ2VzdGlvbg0KICAgICsgSElOVDogeW91IHdpbGwgbmVlZCB0byB1c2UgdGhlIGBnZW9tX2NvbGAgZnVuY3Rpb24gdG8gY3JlYXRlIHRoZSBiYXIgcGxvdA0KPC9kaXY+DQoNCioqKioqKg0KKioqKioqDQoqKioqKioNCg0KIVtdKGh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9zaGVmZmllbGQtYmlvaW5mb3JtYXRpY3MtY29yZS9yLWNyYXNoLWNvdXJzZS9tYXN0ZXIvaW1hZ2VzL3N1bW1hcmlzZV9leGFtcGxlLnBuZykNCg0KDQpgYGB7cn0NCg0KDQoNCmBgYA0KDQoNCiMgSm9pbmluZw0KDQpJbiBtYW55IHJlYWwgbGlmZSBzaXR1YXRpb25zLCBkYXRhIGFyZSBzcHJlYWQgYWNyb3NzIG11bHRpcGxlIHRhYmxlcyBvciBzcHJlYWRzaGVldHMuIFVzdWFsbHkgdGhpcyBvY2N1cnMgYmVjYXVzZSBkaWZmZXJlbnQgdHlwZXMgb2YgaW5mb3JtYXRpb24gYWJvdXQgYSBzdWJqZWN0LCBlLmcuIGEgcGF0aWVudCwgYXJlIGNvbGxlY3RlZCBmcm9tIGRpZmZlcmVudCBzb3VyY2VzLiBJdCBtYXkgYmUgZGVzaXJhYmxlIGZvciBzb21lIGFuYWx5c2VzIHRvIGNvbWJpbmUgZGF0YSBmcm9tIHR3byBvciBtb3JlIHRhYmxlcyBpbnRvIGEgc2luZ2xlIGRhdGEgZnJhbWUgYmFzZWQgb24gYSBjb21tb24gY29sdW1uLCBmb3IgZXhhbXBsZSwgYW4gYXR0cmlidXRlIHRoYXQgdW5pcXVlbHkgaWRlbnRpZmllcyB0aGUgc3ViamVjdC4NCg0KYGRwbHlyYCBwcm92aWRlcyBhIHNldCBvZiBqb2luIGZ1bmN0aW9ucyBmb3IgY29tYmluaW5nIHR3byBkYXRhIGZyYW1lcyBiYXNlZCBvbiBtYXRjaGVzIHdpdGhpbiBzcGVjaWZpZWQgY29sdW1ucy4gRm9yIHRob3NlIGZhbWlsaWFyIHdpdGggc3VjaCBTUUwsIHRoZXNlIG9wZXJhdGlvbnMgYXJlIHZlcnkgc2ltaWxhciB0byBjYXJyeWluZyBvdXQgam9pbiBvcGVyYXRpb25zIGJldHdlZW4gdGFibGVzIGluIGEgcmVsYXRpb25hbCBkYXRhYmFzZS4NCg0KQXMgYSB0b3kgZXhhbXBsZSwgbGV0cyBjb25zaWRlciB0d28gZGF0YSBmcmFtZXMgdGhhdCBzb21lIHJlc3VsdHMgb2YgdGVzdGluZyB3aGV0aGVyIGdlbmVzIEEsIEIgYW5kIEMgYXJlIHNpZ25pZmljYW50IGluIG91ciBzdHVkeSAoZ2VuZSBleHByZXNzaW9uLCBtdXRhdGlvbnMsIGV0Yy4pDQoNCmBgYHtyfQ0KZ2VuZV9yZXN1bHRzIDwtIGRhdGEuZnJhbWUoTmFtZT1MRVRURVJTWzE6M10sIHB2YWx1ZSA9IGMoMC4wMDEsIDAuMSwwLjAxKSkNCmdlbmVfcmVzdWx0cw0KYGBgDQoNCldlIG1pZ2h0IGFsc28gaGF2ZSBhIGRhdGEgZnJhbWUgY29udGFpbmluZyBtb3JlIGRhdGEgYWJvdXQgdGhlIGdlbmVzOyBzdWNoIGFzIHdoaWNoIGNocm9tb3NvbWUgdGhleSBhcmUgbG9jYXRlZCBvbi4gQXMgcGFydCBvZiBvdXIgZGF0YSBpbnRlcnByZXRhdGlvbiB3ZSBtaWdodCBuZWVkIHRvIGtub3cgd2hlcmUgaW4gdGhlIGdlbm9tZSB0aGUgZ2VuZXMgYXJlIGxvY2F0ZWQuDQoNCmBgYHtyfQ0KZ2VuZV9hbm5vIDwtIGRhdGEuZnJhbWUoTmFtZSA9IGMoIkEiLCJCIiwiRCIpLCBjaHJvbW9zb21lPWMoMSwxLDMpKQ0KZ2VuZV9hbm5vDQpgYGANCg0KVGhlcmUgYXJlIHZhcmlvdXMgd2F5cyBpbiB3aGljaCB3ZSBjYW4gam9pbiB0aGVzZSB0d28gdGFibGVzIHRvZ2V0aGVyLiBXZSB3aWxsIGZpcnN0IGNvbnNpZGVyIHRoZSBjYXNlIG9mIGEgImxlZnQgam9pbiIuDQoNCiFbXShodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vc2hlZmZpZWxkLWJpb2luZm9ybWF0aWNzLWNvcmUvci1jcmFzaC1jb3Vyc2UvbWFzdGVyL2ltYWdlcy9sZWZ0LWpvaW4uZ2lmKQ0KDQoqQW5pbWF0ZWQgZ2lmIGJ5IEdhcnJpY2sgQWRlbi1CdWllKg0KDQpgbGVmdF9qb2luYCByZXR1cm5zIGFsbCByb3dzIGZyb20gdGhlIGZpcnN0IGRhdGEgZnJhbWUgcmVnYXJkbGVzcyBvZiB3aGV0aGVyIHRoZXJlIGlzIGEgbWF0Y2ggaW4gdGhlIHNlY29uZCBkYXRhIGZyYW1lLiBSb3dzIHdpdGggbm8gbWF0Y2ggYXJlIGluY2x1ZGVkIGluIHRoZSByZXN1bHRpbmcgZGF0YSBmcmFtZSBidXQgaGF2ZSBgTkFgIHZhbHVlcyBpbiB0aGUgYWRkaXRpb25hbCBjb2x1bW5zIGNvbWluZyBmcm9tIHRoZSBzZWNvbmQgZGF0YSBmcmFtZS4NCg0KQW5pbWF0aW9ucyB0byBpbGx1c3RyYXRlIG90aGVyIHR5cGVzIG9mIGpvaW4gYXJlIGF2YWlsYWJsZSBhdCBbaHR0cHM6Ly9naXRodWIuY29tL2dhZGVuYnVpZS90aWR5LWFuaW1hdGVkLXZlcmJzXShodHRwczovL2dpdGh1Yi5jb20vZ2FkZW5idWllL3RpZHktYW5pbWF0ZWQtdmVyYnMpDQoNCmBgYHtyfQ0KbGVmdF9qb2luKGdlbmVfcmVzdWx0cywgZ2VuZV9hbm5vKQ0KYGBgDQpgcmlnaHRfam9pbmAgaXMgc2ltaWxhciBidXQgcmV0dXJucyBhbGwgcm93cyBmcm9tIHRoZSBzZWNvbmQgZGF0YSBmcmFtZSB0aGF0IGhhdmUgYSBtYXRjaCB3aXRoIHJvd3MgaW4gdGhlIGZpcnN0IGRhdGEgZnJhbWUgYmFzZWQgb24gdGhlIHNwZWNpZmllZCBjb2x1bW4uDQoNCmBgYHtyfQ0KcmlnaHRfam9pbihnZW5lX3Jlc3VsdHMsIGdlbmVfYW5ubykNCmBgYA0KDQpgaW5uZXJfam9pbmAgb25seSByZXR1cm5zIHRob3NlIHJvd3Mgd2hlcmUgbWF0Y2hlcyBjb3VsZCBiZSBtYWRlDQoNCmBgYHtyfQ0KaW5uZXJfam9pbihnZW5lX3Jlc3VsdHMsIGdlbmVfYW5ubykNCmBgYA0KDQoNCioqKioqKg0KKioqKioqDQoqKioqKioNCg0KIyBXcmFwLXVwDQoNCldlIGhhdmUgaW50cm9kdWNlZCBhIGZldyBvZiB0aGUgZXNzZW50aWFsIHBhY2thZ2VzIGZyb20gdGhlIFIgdGlkeXZlcnNlIHRoYXQgY2FuIGhlbHAgd2l0aCBkYXRhIG1hbmlwdWxhdGlvbiBhbmQgdmlzdWFsaXNhdGlvbi4NCg0KIVtdKGh0dHBzOi8vYWJlcmRlZW5zdHVkeWdyb3VwLmdpdGh1Yi5pby9zdHVkeUdyb3VwL2xlc3NvbnMvU0ctVDItSm9pbnRXb3Jrc2hvcC90aWR5dmVyc2UucG5nKQ0KSG9wZWZ1bGx5IHlvdSB3aWxsIGZlZWwgbW9yZSBjb25maWRlbnQgYWJvdXQgaW1wb3J0aW5nIHlvdXIgZGF0YSBpbnRvIFIgYW5kIHByb2R1Y2luZyBzb21lIHVzZWZ1bCB2aXN1YWxpc2F0aW9ucy4gWW91IHdpbGwgcHJvYmFibHkgaGF2ZSBxdWVzdGlvbnMgcmVnYXJkaW5nIHRoZSBhbmFseXNpcyBvZiB5b3VyIG93biBkYXRhLiBTb21lIGdvb2Qgc3RhcnRpbmcgcG9pbnRzIHRvIGdldCBoZWxwIGFyZSBsaXN0ZWQgYmVsb3cuDQoNCjxkaXYgY2xhc3M9ImluZm9ybWF0aW9uIj4NCg0KLSBbdGlkeXZlcnNlIGhvbWVwYWdlXShodHRwczovL3d3dy50aWR5dmVyc2Uub3JnLykNCi0gW1IgZ3JhcGggZ2FsbGVyeV0oaHR0cHM6Ly93d3cuci1ncmFwaC1nYWxsZXJ5LmNvbS8pDQoNCjwvZGl2Pg0KDQpUbyBmaW5pc2ggdGhlIHdvcmtzaG9wIHdlIHdpbGwgbG9vayBhdCB0aGUgYW5hbHlzaXMgb2Ygc29tZSByZWxldmFudCBkYXRhIHRoYXQgd2UgY2FuIGltcG9ydCBpbnRvIFIgYW5kIGFuYWx5c2Ugd2l0aCB0aGUgdG9vbHMgZnJvbSB0aGUgd29ya3Nob3AuDQoNCiMjIEludHJvZHVjaW5nIHRoZSBDT1ZJRC0xOSBkYXRhIA0KDQpEYXRhIGZvciBnbG9iYWwgQ09WSUQtMTkgY2FzZXMgYXJlIGF2YWlsYWJsZSBvbmxpbmUgZnJvbSBDU1NFIGF0IEpvaG5zIEhvcGtpbnMgVW5pdmVyc2l0eSBvbiB0aGVpciBnaXRodWIgcmVwb3NpdG9yeS4NCg0KPGRpdiBjbGFzcz0iaW5mb3JtYXRpb24iPg0KW2dpdGh1Yl0od3d3LmdpdGh1Yi5jb20pIGlzIGFuIGV4Y2VsbGVudCB3YXkgb2YgbWFraW5nIHlvdXIgY29kZSBhbmQgYW5hbHlzaXMgYXZhaWxhYmxlIGZvciBvdGhlcnMgdG8gcmV1c2UgYW5kIHNoYXJlLiBQcml2YXRlIHJlcG9zaXRvcmllcyB3aXRoIHJlc3RyaWN0ZWQgYWNjZXNzIGFyZSBhbHNvIGF2YWlsYWJsZS4gSGVyZSBpcyBhIHVzZWZ1bCBiZWdpbm5lcnMgZ3VpZGUuDQoNCi1bRnJpZW5kbHkgZ2l0aHViIGludHJvXShodHRwczovL2tpcnN0aWVqYW5lLmdpdGh1Yi5pby9mcmllbmRseS1naXRodWItaW50cm8vKQ0KDQo8L2Rpdj4NCg0KUiBpcyBjYXBhYmxlIG9mIGRvd25sb2FkaW5nIGZpbGVzIHRvIG91ciBvd24gbWFjaGluZSBzbyB3ZSBjYW4gYW5hbHlzZSB0aGVtLiBXZSBuZWVkIHRvIGtub3cgdGhlIFVSTCAoZm9yIHRoZSBDT1ZJRCBkYXRhIHdlIGNhbiBmaW5kIHRoaXMgZnJvbSBnaXRodWIsIG9yIHVzZSB0aGUgYWRkcmVzcyBiZWxvdykgYW5kIGNhbiBzcGVjaWZ5IHdoYXQgdG8gY2FsbCB0aGUgZmlsZSB3aGVuIGl0IGlzIGRvd25sb2FkZWQuDQoNCmBgYHtyfQ0KZG93bmxvYWQuZmlsZSgiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL0NTU0VHSVNhbmREYXRhL0NPVklELTE5L21hc3Rlci9jc3NlX2NvdmlkXzE5X2RhdGEvY3NzZV9jb3ZpZF8xOV90aW1lX3Nlcmllcy90aW1lX3Nlcmllc19jb3ZpZDE5X2NvbmZpcm1lZF9nbG9iYWwuY3N2IixkZXN0ZmlsZSA9ICJ0aW1lX3Nlcmllc19jb3ZpZDE5X2NvbmZpcm1lZF9nbG9iYWwuY3N2IikNCg0KYGBgDQoNCldlIGNhbiB1c2UgdGhlIGByZWFkX2NzdmAgZnVuY3Rpb24gYXMgYmVmb3JlIHRvIGltcG9ydCB0aGUgZGF0YSBhbmQgdGFrZSBhIGxvb2suIFdlIGNhbiBzZWUgdGhlIGJhc2ljIHN0cnVjdHVyZSBvZiB0aGUgZGF0YSBpcyBvbmUgcm93IGZvciBlYWNoIGNvdW50cnkgLyByZWdpb24gYW5kIGNvbHVtbnMgZm9yIGNhc2VzIG9uIGVhY2ggZGF5LiANCg0KYGBge3J9DQpjb3ZpZCA8LSByZWFkX2NzdigidGltZV9zZXJpZXNfY292aWQxOV9jb25maXJtZWRfZ2xvYmFsLmNzdiIpDQpjb3ZpZA0KYGBgDQoNCldlIGNhbiBwb3RlbnRpYWxseSBqb2luIHRoZXNlIGRhdGEgdG8gYGdhcG1pbmRlcmAsIGJ1dCBpdCB3b3VsZCBiZSBiZW5lZmljaWFsIHRvIGhhdmUgb25lIGNvbHVtbiBuYW1lIGluIGNvbW1vbiBiZXR3ZWVuIGJvdGggZmlsZXMuIFdlIGNhbiBgcmVuYW1lYCB0aGUgYENvdW50cnkvUmVnaW9uYCBjb2x1bW4gb2Ygb3VyIG5ldyBkYXRhIGZyYW1lIHRvIG1hdGNoIGBnYXBtaW5kZXJgLg0KDQpgYGB7cn0NCmNvdmlkIDwtIHJlYWRfY3N2KCJ0aW1lX3Nlcmllc19jb3ZpZDE5X2NvbmZpcm1lZF9nbG9iYWwuY3N2IikgJT4lIA0KICByZW5hbWUoY291bnRyeSA9IGBDb3VudHJ5L1JlZ2lvbmApIA0KY292aWQNCmBgYA0KDQpNdWNoIG9mIHRoZSBhbmFseXNpcyBvZiB0aGlzIGRhdGFzZXQgaGFzIGxvb2tlZCBhdCB0cmVuZHMgb3ZlciB0aW1lIChlLmcuIGluY3JlYXNpbmcgL2RlY3JlYXNpbmcgY2FzZSBudW1iZXJzLCBjb21wYXJpbmcgdHJhamVjdG9yaWVzKS4gQXMgd2Uga25vdyBieSBub3csIHRoZSBgZ2dwbG90MmAgcGFja2FnZSBhbGxvd3MgdXMgdG8gbWFwIGNvbHVtbnMgKHZhcmlhYmxlcykgaW4gb3VyIGRhdGFzZXQgdG8gYXNwZWN0cyBvZiB0aGUgcGxvdC4gDQoNCkluIG90aGVyIHdvcmRzLCB3ZSB3b3VsZCBleHBlY3QgdG8gY3JlYXRlIHBsb3RzIGJ5IHdyaXRpbmcgY29kZSBzdWNoIGFzOi0NCg0KDQpgYGANCmdncGxvdChjb3ZpZCwgYWVzKHggPSBEYXRlLCB5ID0uLi4pKSArIC4uLg0KYGBgDQoNClVuZm9ydHVuYXRlbHkgc3VjaCBwbG90cyBhcmUgbm90IHBvc3NpYmxlIHdpdGggdGhlIGRhdGEgaW4gaXQncyBjdXJyZW50IGZvcm1hdC4gQ291bnRzIGZvciBlYWNoIGRhdGUgYXJlIGNvbnRhaW5pbmcgaW4gYSBkaWZmZXJlbnQgY29sdW1uLiBXaGF0IHdlIHJlcXVpcmUgaXMgYSBjb2x1bW4gdG8gaW5kaWNhdGUgdGhlIGRhdGUsIGFuZCB0aGUgY29ycmVzcG9uZGluZyBjb3VudCBpbiB0aGUgbmV4dCBjb2x1bW4uIFN1Y2ggZGF0YSBhcnJhbmdlbWVudHMgYXJlIGtub3duIGFzICpsb25nIGRhdGEqOyB3aGVyZWFzIHdlIGhhdmUgKndpZGUqIGRhdGEuIEZvcnR1bmF0ZWx5IHdlIGNhbiBjb252ZXJ0IGJldHdlZW4gdGhlIHR3byB1c2luZyB0aGUgYHRpZHlyYCBwYWNrYWdlIChhbHNvIHBhcnQgb2YgdGlkeXZlcnNlKS4NCg0KYGBge3IgZXZhbD1GQUxTRX0NCiMjIGluc3RhbGwgdGlkeXIgaWYgeW91IGRvbid0IGFscmVhZHkgaGF2ZSBpdA0KaW5zdGFsbC5wYWNrYWdlcygidGlkeXIiKQ0KYGBgDQoNCg0KPGRpdiBjbGFzcz0iaW5mb3JtYXRpb24iPg0KDQpGb3IgbW9yZSBpbmZvcm1hdGlvbiBvbiAqdGlkeSBkYXRhKiwgYW5kIGhvdyB0byBjb252ZXJ0IGJldHdlZW4gbG9uZyBhbmQgd2lkZSBkYXRhLCBzZWUNCg0KaHR0cHM6Ly9yNGRzLmhhZC5jby5uei90aWR5LWRhdGEuaHRtbA0KDQo8L2Rpdj4NCg0KYGBge3J9DQoNCmNvdmlkIDwtIHJlYWRfY3N2KCJ0aW1lX3Nlcmllc19jb3ZpZDE5X2NvbmZpcm1lZF9nbG9iYWwuY3N2IikgJT4lIA0KICByZW5hbWUoY291bnRyeSA9IGBDb3VudHJ5L1JlZ2lvbmApICU+JSANCiAgdGlkeXI6OnBpdm90X2xvbmdlcig1Omxhc3RfY29sKCksbmFtZXNfdG89IkRhdGUiLCB2YWx1ZXNfdG89IkNhc2VzIikNCmNvdmlkDQpgYGANCg0KQW5vdGhlciBwb2ludCB0byBub3RlIGlzIHRoYXQgdGhlIGRhdGVzIGFyZSBub3QgaW4gYW4gaW50ZXJuYXRpb25hbGx5IHJlY29nbmlzZWQgZm9ybWF0LCB3aGljaCBjb3VsZCBjYXVzZSBhIHByb2JsZW0gZm9yIHNvbWUgdmlzdWFsaXNhdGlvbnMgdGhhdCByZWx5IG9uIGRhdGUgb3JkZXIuIFdlIGNhbiBmaXggYnkgZXhwbGljaXRseSBjb252ZXJ0aW5nIHRvIFlZWVktTU0tREQgZm9ybWF0Lg0KDQpgYGB7cn0NCmNvdmlkIDwtIHJlYWRfY3N2KCJ0aW1lX3Nlcmllc19jb3ZpZDE5X2NvbmZpcm1lZF9nbG9iYWwuY3N2IikgJT4lIA0KICByZW5hbWUoY291bnRyeSA9IGBDb3VudHJ5L1JlZ2lvbmApICU+JSANCiAgdGlkeXI6OnBpdm90X2xvbmdlcig1Omxhc3RfY29sKCksbmFtZXNfdG89IkRhdGUiLCB2YWx1ZXNfdG89IkNhc2VzIikgJT4lIA0KICAgIG11dGF0ZShEYXRlPWFzLkRhdGUoRGF0ZSwiJW0vJWQvJXkiKSkNCmNvdmlkDQpgYGANCg0KVGhlIG51bWJlciBvZiBuZXcgY2FzZXMgcGVyLWRheSBjYW4gYWxzbyBiZSBhZGRlZCB0byB0aGUgdGFibGUgYnkgdXNpbmcgdGhlIGBsYWdgIGZ1bmN0aW9uIGZyb20gYGRwbHlyYC4NCg0KYGBge3J9DQpjb3ZpZCA8LSByZWFkX2NzdigidGltZV9zZXJpZXNfY292aWQxOV9jb25maXJtZWRfZ2xvYmFsLmNzdiIpICU+JSANCiAgcmVuYW1lKGNvdW50cnkgPSBgQ291bnRyeS9SZWdpb25gKSAlPiUgDQogIHRpZHlyOjpwaXZvdF9sb25nZXIoNTpsYXN0X2NvbCgpLG5hbWVzX3RvPSJEYXRlIiwgdmFsdWVzX3RvPSJDYXNlcyIpICU+JSANCiAgbXV0YXRlKERhdGU9YXMuRGF0ZShEYXRlLCIlbS8lZC8leSIpKSAlPiUgDQogIG11dGF0ZShEYWlseUNhc2VzID0gQ2FzZXMgLSBkcGx5cjo6bGFnKENhc2VzLGRlZmF1bHQgPSAwKSkgIA0KY292aWQNCmBgYA0KDQoNCiMjIyBFeGVyY2lzZSANCjxkaXYgY2xhc3M9ImV4ZXJjaXNlIj4NCldoYXQgcGxvdHMgYW5kIHN1bW1hcmllcyBjYW4geW91IG1ha2UgZnJvbSB0aGVzZSBkYXRhPw0KDQotIFBsb3R0aW5nIHRoZSBudW1iZXIgb2YgY2FzZXMgb3ZlciB0aW1lIGZvciBjZXJ0YWluIGNvdW50cmllcw0KLSBGb3IgZWFjaCBjb250aW5lbnQsIHdoYXQgY291bnRyaWVzIGhhdmUgdGhlIGhpZ2hlc3QgbnVtYmVyIG9mIGNhc2VzPw0KLSBDYW4geW91IG5vcm1hbGlzZSBmb3IgcG9wdWxhdGlvbiBzaXplICh1c2luZyAyMDA3IHBvcHVsYXRpb24gZmlndXJlcyk/DQoNCjwvZGl2Pg0KDQpgYGB7cn0NCg0KYGBgDQoNCg==